using System;
using System.Collections;
using System.IO;
using System.Security.Policy;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows.Forms;
using Crawler;
using mshtml;

namespace Crawler
{
	class Parser
	{
		IHTMLDocument2 doc;
		String domain;
		private ICentralFactory factory;
		private IUrlStack urlStack;
		private PathExtractor pathExtractor;
		private string htmlContent;
		private IKey htmlPageKey;

		public Parser(string domain, ICentralFactory factory)
		{
			this.domain = domain;
			this.factory = factory;
			this.urlStack = this.factory.GetUrlStack();
			pathExtractor = new PathExtractor(factory);
		}

		public String HtmlContent
		{
			set { this.htmlContent = value; }
			get { return this.htmlContent; }
		}

		public IKey HtmlPageKey
		{
			set {this.htmlPageKey = value;}
			get {return this.htmlPageKey ;}
		}

		StreamWriter swFromFile;

		public void ParseString()
		{
			this.doc = new HTMLDocumentClass();
			doc.write(this.HtmlContent);
			doc.close();
			this.PushElements();
			ParserPool.count--;
			foreach (IHTMLElement e in this.doc.all)
			{
				if (e.tagName.ToLower() == "html".ToLower())
				{
					this.HtmlContent = e.outerHTML;
				}
			}
		}

		Regex regex = new Regex(@"^[a-zA-z0-9/]+\.\w+$", RegexOptions.IgnoreCase | RegexOptions.Compiled);

		Regex matchPopularExtensionsInJavaScriptHrefs =
			new Regex(@"\w+\.(htm|jpg|pdf)", RegexOptions.Compiled | RegexOptions.IgnoreCase |
				RegexOptions.Compiled);

		private String CheckURL(String str)
		{
			str = str.ToLower();
			Match match = regex.Match(str);
			string result = "";
			if (str.StartsWith("http://") || str.StartsWith("file://"))
			{
				result = str;
			}
			else if (str.StartsWith("about:blank"))
			{
				str = str.Replace("about:blank", "");
			}
			else if (str.StartsWith(@"."))
			{
				str = str.Substring(1);
				if (str.StartsWith(@"/"))
					str = str.Substring(1);
			}
			else if (str.StartsWith(@"javascript:"))
			{
				Match m = matchPopularExtensionsInJavaScriptHrefs.Match(str);
				if (m.Success)
					str = m.Groups[0].ToString();
			}
			else if (match.Success)
				result = str;
			else throw new ArgumentException("");
			return result = str;
		}

		public void PushElements()
		{
			this.PushAnchors();
			this.PushImages();
			this.PushEmbeds();
			this.PushFrames();
			this.PushStyleSheets();
		}

		public String Domain
		{
			get { return this.domain; }
			set { this.domain = value; }
		}

		private void PushAnchors()
		{
			foreach (IHTMLElement el in this.doc.links)
			{
				lock (typeof (UrlStack))
				{
					try
					{
						UpdateDOMWithPatchedUrl(el, "href");
					}
					catch (ArgumentException e)
					{
					}
				}
			}
		}

		private void UpdateDOMWithPatchedUrl(IHTMLElement el, string tagName)
		{
			string tagUrl = el.getAttribute(tagName, 0).ToString();
			string sourceUrl = CheckURL(tagUrl);
			if (!sourceUrl.StartsWith(@"http://"))
			{
				el.setAttribute(tagName, HandleFilePatching(tagUrl, sourceUrl), 0);
				this.urlStack.Add(new Url(ValidateUrl(GetPathFromKey() + sourceUrl)), false);
			}
			else if (sourceUrl.IndexOf(factory.GetDomain()) > 0)
			{
				el.setAttribute(tagName, HandleFilePatching(tagUrl, sourceUrl), 0);
				this.urlStack.Add(new Url(ValidateUrl(factory.GetDomain() + sourceUrl)), false);
			}
		}

		private string ValidateUrl(string url)
		{
			int doubleSlashPos = url.IndexOf(@"//");
			if (url.IndexOf(@"//") != url.LastIndexOf(@"//"))
			{
				url = url.Replace(@"//", @"/");
				url = url.Insert(doubleSlashPos, @"/");
			}
			return url;
		}

		private string HandleFilePatching(string tagUrl, string sourceUrl)
		{
			string patchedUrl = "";
			if (tagUrl.StartsWith(@"javascript:"))
				patchedUrl = tagUrl.Replace(sourceUrl, PatchURL(sourceUrl));
			else
				patchedUrl = PatchURL(sourceUrl);
			return patchedUrl;
		}

		private void PushImages()
		{
			foreach (IHTMLElement el in this.doc.images)
			{
				lock (typeof (UrlStack))
				{
					UpdateDOMWithPatchedUrl(el, "src");
				}
			}

		}

		private void PushStyleSheets()
		{
			foreach (Object el in this.doc.styleSheets)
			{
				lock (typeof (UrlStack))
				{
					IHTMLStyleSheet e = el as IHTMLStyleSheet;
					try
					{
						UpdateDOMWithPatchedStyleSheet(e);
					}
					catch (ArgumentException ee)
					{
					}
				}
			}
		}

		private void UpdateDOMWithPatchedStyleSheet(IHTMLStyleSheet e)
		{
			string sourceUrl = CheckURL(e.href);
			if (!sourceUrl.StartsWith(@"http://"))
			{
				e.href = PatchURL(sourceUrl);
				this.urlStack.Add(new Url(ValidateUrl(GetPathFromKey() + sourceUrl)), false);
			}

			else if (sourceUrl.IndexOf(factory.GetDomain()) > 0)
			{
				e.href = PatchURL(sourceUrl);
				this.urlStack.Add(new Url(ValidateUrl(factory.GetDomain() + sourceUrl)), false);
			}
		}

		private void PushEmbeds()
		{
			foreach (IHTMLElement el in this.doc.embeds)
			{
				lock (typeof (UrlStack))
				{
					UpdateDOMWithPatchedUrl(el, "src");
				}
			}
		}

		private void PushFrames()
		{
			foreach (IHTMLElement e in this.doc.all)
			{
				if (e.tagName.ToLower() == "FRAME".ToLower() ||
					e.tagName.ToLower() == "IFRAME".ToLower())
				{
					try
					{
						string sourceUrl = CheckURL(e.getAttribute("src", 0).ToString());
						if (!sourceUrl.StartsWith(@"http://"))
						{
							this.urlStack.Add(new Url(ValidateUrl(factory.GetDomain() + sourceUrl)), false);
							e.setAttribute("src", PatchURL(sourceUrl), 0);
						}
					}
					catch (Exception ee)
					{
						//MessageBox.Show (ee.Message + "" + ee.Source );
					}
				}
			}
		}


		private string PatchURL(string url)
		{
			string localPath = pathExtractor.ExtractRelativePathFromUrl(new Key(url.Insert(0, this.domain)));
			return localPath;
			//this.htmlContent = this.htmlContent.Replace(url, localPath);
			WriteToFile(@"e:\testdata\test.txt", this.HtmlContent);
		}

		private void WriteToFile(string fileName, string content)
		{
			FileStream fs = new FileStream(fileName,
			                               FileMode.Create, FileAccess.Write, FileShare.None);

			swFromFile = new StreamWriter(fs);
			swFromFile.Write(content);
			swFromFile.Flush();
			swFromFile.Close();
		}

		private string GetPathFromKey()
		{
			string pathString = this.htmlPageKey.ToString();
			int positionOfLastSlash = pathString.LastIndexOf(@"/");
			pathString = pathString.Substring(0,positionOfLastSlash+1); 
			return pathString;
		}
	}


	/// <summary>
	/// Summary description for Class1.
	/// </summary>
	class QPair
	{
		System.Object first;
		System.Object second;

		public QPair(System.Object one, System.Object two)
		{
			this.first = one;
			this.second = two;
		}

		public System.Object getFirst()
		{
			return this.first;
		}

		public System.Object getSecond()
		{
			return this.second;
		}
	}

	public class ParserPool : IParserPool
	{
		protected int maxThreads;
		public static int count;
		protected Queue WaitingList;
		protected IList ParserList = new ArrayList();
		public int notificationCount = 0;
		public bool parsing;

		public ParserPool(int max_threads)
		{
			this.maxThreads = max_threads;
			this.WaitingList = new Queue();

		}

		public string ParsePage(Dumpable dumpable, IKey pageKey)
		{
			string result = "";
			parsing = true;
			this.WaitingList.Enqueue(new QPair(dumpable.StreamDump(), CentralFactory.centralFactory.GetDomain()));
			if (ParserPool.count < this.maxThreads)
			{
				result = this.newParser(dumpable, pageKey);
			}
			parsing = false;
			return result;
		}

		private string newParser(Dumpable dumpable, IKey pageKey)
		{
			Parser p = new Parser(CentralFactory.centralFactory.GetDomain(), CentralFactory.centralFactory);
			ParserPool.count++;
			p.HtmlContent = dumpable.StreamDump();
			p.HtmlPageKey = pageKey;
			//this.ParserList.Add(new Thread(new ThreadStart(p.ParseString))) ;
			p.ParseString();
			return p.HtmlContent;
		}


		public void NotifyWhenReferenceAddedToDownloadStore(object sender, ReferenceAddedToDownloadStoreArgs args)
		{
			this.notificationCount = args.GetCount();
			ICentralFactory factory = CentralFactory.centralFactory;
			while (this.notificationCount > 0 && this.parsing == false)
			{
				IKey key = args.NextKey();
				IResourceReference currentResourceReference = factory.GetDownloadStore().GetResource(key);
				if (Object.ReferenceEquals(currentResourceReference, ResourceReference.NULL_REFERENCE))
				{
					this.notificationCount--;
					continue;
				}
				if (currentResourceReference is Dumpable)
					currentResourceReference.SetContent(ParsePage(currentResourceReference as Dumpable, key));
				IMapper mapper = factory.GetMapper();
				IPathExtractor pathExtractor = new PathExtractor(factory);
				mapper.Add(key, pathExtractor.ExtractAbsolutePathFromUrl(key));

				this.notificationCount--;
			}
			factory.GetUrlStack().TriggerNotifications();
		}
	}

}